<html>
	<head>
		<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
		<link rel="preconnect" href="https://fonts.googleapis.com">
		<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
		<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
		<meta charset="utf8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>VDON Speed Test</title>
		<style>
			:root {
			  --primary-bg: #333;
			  --secondary-bg: #444;
			  --card-bg: #3a3a3a;
			  --text-color: white;
			  --accent-color: #4296f5;
			  --accent-hover: #2979e2;
			  --success-color: #60ff60;
			  --warning-color: #f3f304;
			  --error-color: #fb4949;
			  --border-color: #666;
			}

			body {
			  text-align: center;
			  background: var(--primary-bg);
			  font-family: 'Noto Sans', sans-serif;
			  color: var(--text-color);
			  margin: 0;
			  padding: 0;
			  line-height: 1.6;
			}

			h1, h2, h3 {
			  margin-top: 1rem;
			  margin-bottom: 1rem;
			}

			h2 {
			  width: 760px;
			  max-width: 90%;
			  margin: auto;
			}

			a {
			  color: white;
			  text-decoration: none;
			}

			a:hover {
			  text-decoration: underline;
			}

			#mainapp {
			  max-width: 1200px;
			  margin: 0 auto;
			  padding: 1rem;
			}

			#container {
			  background: var(--secondary-bg);
			  border-radius: 8px;
			  padding: 1.5rem;
			  margin: 1.5rem auto;
			  max-width: 900px;
			  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
			}

			#graphs {
			  display: flex;
			  flex-wrap: wrap;
			  justify-content: center;
			  gap: 1.5rem;
			  margin: 2rem auto;
			  max-width: 1100px;
			}

			.graph {
			  background: var(--card-bg);
			  border-radius: 8px;
			  padding: 0;
			  width: 300px;
			  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
			}

			.graph h3 {
			  margin-top: 0;
			  margin-bottom: 0.5rem;
			}

			.graph span {
			  display: block;
			  font-size: 1.5rem;
			  font-weight: bold;
			  margin-bottom: 0.5rem;
			}

			canvas {
			  width: 100%;
			  height: 100px;
			  background-color: rgba(0, 0, 0, 0.2);
			  border-radius: 4px;
			}

			#details {
			  background: var(--secondary-bg);
			  border-radius: 8px;
			  padding: 1.5rem;
			  margin: 1.5rem auto;
			  max-width: 900px;
			  text-align: left;
			  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
			}

			button {
			  background-color: var(--accent-color);
			  color: white;
			  border: none;
			  border-radius: 4px;
			  padding: 10px 15px;
			  cursor: pointer;
			  transition: background-color 0.3s;
			}

			button:hover {
			  background-color: var(--accent-hover);
			}

			.hidden {
			  display: none !important;
			}

			#detailed-info {
			  margin-top: 30px;
			  padding: 20px;
			  border-top: 1px solid var(--border-color);
			  background: rgba(0, 0, 0, 0.1);
			  border-radius: 0 0 8px 8px;
			}

			.info-buttons {
			  display: flex;
			  flex-wrap: wrap;
			  gap: 15px;
			  justify-content: center;
			  margin-top: 15px;
			}

			.info-buttons button {
			  background-color: var(--accent-color);
			  color: white;
			  border: none;
			  border-radius: 4px;
			  padding: 10px 15px;
			  cursor: pointer;
			  font-size: 14px;
			  transition: background-color 0.3s;
			}

			.info-buttons button:hover {
			  background-color: var(--accent-hover);
			}

			.success {
			  color: var(--success-color);
			}

			.warning {
			  color: var(--warning-color);
			}

			.error {
			  color: var(--error-color);
			}

			/* Blink animation for loading text */
			@keyframes fadein {
			  0%   {opacity: 0.5;}
			  100% {opacity: 1;}
			}

			blink {
			  animation: blink 1s step-end infinite;
			}

			@keyframes blink {
			  50% { opacity: 0; }
			}

			/* Make design responsive */
			@media (max-width: 768px) {
			  #graphs {
				flex-direction: column;
				align-items: center;
			  }
			  
			  .graph {
				width: 90%;
			  }
			}
		</style>
	</head>
	<body>
		<div id="mainapp" >
			<h1>
				Video and stream quality check results
			</h1>
			<div id="container">
				<h3><blink><span style='color:#85c0ff'>RESULTS ARE LOADING...</span></blink><h3>
			</div>
			<div id="graphs">
				<div class="graph">
					<h3>Bitrate (kbps)</h3>
					<span>0</span>
					<canvas id="bitrate-graph"></canvas>
				</div>

				<div class="graph">
					<h3>Buffer delay (ms)</h3>
					<span>0</span>
					<canvas id="buffer-graph"></canvas>
				</div>

				<div class="graph">
					<h3>Packet Loss (%)</h3>
					<span>0</span>
					<canvas id="packetloss-graph"></canvas>
				</div>
			</div>
			<div id="details">
			</div>
			
		</div>

		<script>
		
			function getChromiumVersion() {
				var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
				return raw ? parseInt(raw[2], 10) : false;
			}
			
			function next1(){
				document.getElementById("page1").classList.add("hidden");
				document.getElementById("page2").classList.remove("hidden");
			}
			
			function next2(){
				document.getElementById("page2").classList.add("hidden");
				document.getElementById("page3").classList.remove("hidden");
				loadIframe();
			}
			
			function next3(){
				document.getElementById("page3").classList.add("hidden");
				document.getElementById("mainapp").classList.remove("hidden");
				loadIframe();
			}
		
			(function (w) {
				w.URLSearchParams =
					w.URLSearchParams ||
					function (searchString) {
						var self = this;
						self.searchString = searchString;
						self.get = function (name) {
							var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
								self.searchString
							);
							if (results == null) {
								return null;
							} else {
								return decodeURI(results[1]) || 0;
							}
						};
					};
			})(window);
			var urlParams = new URLSearchParams(window.location.search);
			
		
			
			function copyFunction(copyText) {
				alert("Log copied to the clipboard.");
				try {
					copyText.select();
					copyText.setSelectionRange(0, 99999);
					document.execCommand("copy");
				} catch (e) {
					var dummy = document.createElement("input");
					document.body.appendChild(dummy);
					dummy.value = copyText;
					dummy.select();
					document.execCommand("copy");
					document.body.removeChild(dummy);
					return false;
				}
				
			}
			
			function timeConverter(UNIX_timestamp){
			  var a = new Date(UNIX_timestamp);
			  var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
			  var year = a.getFullYear();
			  var month = months[a.getMonth()];
			  var date = a.getDate();
			  
			  var hours = a.getHours();
			  var minutes = a.getMinutes();
			  var ampm = hours >= 12 ? 'pm' : 'am';
			  hours = hours % 12;
			  hours = hours ? hours : 12; // the hour '0' should be '12'
			  minutes = minutes < 10 ? '0'+minutes : minutes;
			  var strTime = hours + ':' + minutes + ' ' + ampm;
			  
			  var time = date + ' ' + month + ' ' + year + ' ' + hours + ':' + minutes + ampm;
			  return time;
			}
			
			function printValues(obj) {
				var out = "";
				for (var key in obj) {
					if (typeof obj[key] === "object") {
						out += "<br />";
						out += printValues(obj[key]);
					} else {
						if (key.startsWith("_")) {
						} else {
							out += "<b>" + key + "</b>: " + obj[key] + "<br />";
						}
					}
				}
				return out;
			}


			var streamID = "";
			if (urlParams.has("id")) {
				streamID = urlParams.get("id");
			}
		
			var bitrate = {
				element: "bitrate-graph",
				data: 0,
				max: 6000,
				target: 2500,
			};
			var frames;
			var buffer = {
				element: "buffer-graph",
				data: 0,
				max: 200,
				target: 100,
			};
			var packetloss = {
				element: "packetloss-graph",
				data: 0,
				max: 3,
				target: 2,
			};
			
			// https://record.vdo.workers.dev/?name="+recordResults
			
			var QLR_1 = 0;
			var QLR_2 = 0;
			var QLR_3 = 0;
			
			var BBB = 0;
			var counter = 0;
			
			var BUFF = 0;
			var BUFFCCC = 0;
			
			var PAK = 0;
			var PAKCCC = 0;
			
			var packetLossSpikes = 0;
			
			function process(arr) {
				  var maxResolution = "0 x 0";
				  console.log(arr);
				  
				  // Clear container
				  document.getElementById("container").innerHTML = "<h3>Quality Test Results</h3>";
				  
				  arr.forEach(data => {
					if ("bitrate" in data) {
					  updateData("bitrate", data.bitrate);
					  if (data.bitrate !== null) {
						BBB += data.bitrate;
						counter += 1;
					  }
					}
					if ("target" in data) {
					  updateData("target", data.target);
					}
					if ("buffer" in data) {
					  updateData("buffer", data.buffer);
					  if (data.buffer !== null) {
						BUFF += data.buffer;
						BUFFCCC += 1;
					  }
					}
					if (data.packetloss !== undefined && !isNaN(data.packetloss)) {
					  updateData("packetloss", data.packetloss);
					  if (data.packetloss !== null) {
						PAK += parseFloat(data.packetloss) || 0;
						PAKCCC += 1;
					  
						if (parseFloat(data.packetloss) > 2.0) {
						  packetLossSpikes++;
						}
					  }
					}
					
					if (data.timestart) {
					  document.getElementById("details").innerHTML += "<br /><b>Test start time:</b> " + timeConverter(data.timestart) + "<br />";
					}
					if ("peakhour" in data){
						if (!data.peakhour){
							document.getElementById("details").innerHTML += "<small><i>(This test was completed outside of peak streaming hours.</br>Results are normally worse during peak hours.)</i></small><br />";

						}
					}
					
					if (data.codecs) {
						var videoCodecs = Object.keys(data.codecs.video).join(', ');
						var audioCodecs = Object.keys(data.codecs.audio).join(', ');
						
						if (videoCodecs) {
							document.getElementById("details").innerHTML += "<br /><b>Supported video codecs:</b> " + videoCodecs + "<br />";
						}
						if (audioCodecs) {
							document.getElementById("details").innerHTML += "<b>Supported audio codecs:</b> " + audioCodecs + "<br />";
						}
						
						// Check for optimal codecs
						if (data.codecs.video.h264 || data.codecs.video.vp9) {
							document.getElementById("details").innerHTML += "<small><i>(Good codec support for streaming)</i></small><br />";
						} else if (!data.codecs.video.h264) {
							document.getElementById("details").innerHTML += "<small><i>(H.264 codec support not detected, which may limit compatibility)</i></small><br />";
						}
					}

					if (data.userRegion) {
						document.getElementById("details").innerHTML += "<br /><b>User region:</b> " + data.userRegion + "<br />";
					}
					
					if (("hostMode" in data) && ("srflxMode" in data)){
						if (!data.hostMode && !data.srflxMode){
							
							document.getElementById("details").innerHTML += "<h3><span style='color:#fb4949'>WARNING: </span>Connection will be relayed via TURN server</h3>";
							document.getElementById("details").innerHTML += "<u>No host or SRFLX ICE candidates collected. The browser or extension is likely blocking IP leaks</u><br />";
						}
					}
					
					if (data.summary){
						if (data.summary.download && data.summary.upload){
							document.getElementById("details").innerHTML += "<br /><b>Max download bandwidth:</b> "+parseInt(data.summary.download/(1024*1024))+"-Mbps<br />";
							document.getElementById("details").innerHTML += "<br /><b>Max Upload bandwidth:</b> "+parseInt(data.summary.upload/(1024*1024))+"-Mbps<br />";
						}
					}
					//if (data.scores && data.scores.gaming && data.scores.gaming.classificationName && data.scores.rtc && data.scores.rtc.classificationName && data.scores.streaming && data.scores.streaming.classificationName){
					//	document.getElementById("details").innerHTML += "<br /><b>Gaming:</b> "+data.scores.gaming.classificationName+", <b>RTC:</b> "+data.scores.rtc.classificationName+", <b>Streaming:</b> //"+data.scores.streaming.classificationName+"<br />";
					//}
					
					if ("resolution" in data){
						try {
							if (parseInt(data.resolution.split("x ")[1])>parseInt(maxResolution.split("x ")[1])){
								maxResolution = data.resolution;
							}
						} catch(e){}
						//updateData("resolution", data.resolution);
					}
					
					if ("QLR" in data){
						if (data.QLR == "none"){
							QLR_1 +=1;
						} else if (data.QLR.toLowerCase() == "cpu"){
							QLR_2 +=1;
						} else if (data.QLR.toLowerCase() == "network"){
							QLR_3 +=1;
						}
					}
					if ("info" in data){
						if (data.info.Browser){
							document.getElementById("details").innerHTML += "<br /><b>Browser used:</b> "+data.info.Browser+"<br />";
							if (!data.info.Browser.startsWith("Chromium")){
								document.getElementById("details").innerHTML += "<h3>A Chromium-based browser is recommended.</h3>";
							} 
						}
						if ("plugged_in" in data.info){
							if (!data.info.plugged_in){
								document.getElementById("details").innerHTML += "<h3>The user's power is not plugged in</h3>";
							}
						}
						if (data.info.platform){
							document.getElementById("details").innerHTML += "<br /><b>Platform (os):</b> "+data.info.platform+"<br />";
							if (data.info.platform.toLowerCase() == "macintel"){
								document.getElementById("details").innerHTML += "<i>(Intel or Apple Silicon)</i><br />";
							}
						}
						if (data.info.gpGPU){
							document.getElementById("details").innerHTML += "<br /><b>GPU:</b> "+data.info.gpGPU+"<br />";
						}
						if (data.info.CPU){
							document.getElementById("details").innerHTML += "<br /><b>CPU:</b> "+data.info.CPU+"<br />";
						}
						
						if (data.info.conn_type){
							document.getElementById("details").innerHTML += "<br /><b>Connection type:</b> "+data.info.conn_type+"<br />";
							if (data.info.conn_type == "wifi"){
								document.getElementById("details").innerHTML += "<h3>Avoid using WiFi if at all possible</h3>";
							}
							
						}
					}
					if (data.encoder){
						if (data.encoder.toLowerCase() == "libvpx"){
							document.getElementById("details").innerHTML += "<br /><b>Default video codec used:</b> VP8<br />";
						} else {
							document.getElementById("details").innerHTML += "<br /><b>Default video codec used:</b> "+data.encoder.toUpperCase()+"<br />";
						}
					}
				});
				// container
				
				if (maxResolution){
					document.getElementById("details").innerHTML += "<br /><b>Highest video resolution reached:</b> "+maxResolution+"<br />";
				}
				
				
				 // Apply styling to the results messages
				  var total = QLR_1 + QLR_2 + QLR_3;
				  if (QLR_2/total > 0.5) {
					document.getElementById("container").innerHTML += "<p class='error'>Serious CPU overload issues. Consider reducing the capture resolution.</p>";
				  } else if (QLR_2/total > 0.1) {
					document.getElementById("container").innerHTML += "<p>Occasional CPU overload issues. Consider reducing the capture resolution.</p>";
				  }
				  
				  if (QLR_3/total > 0.5) {
					document.getElementById("container").innerHTML += "<p class='error'>The network quality or bandwidth limited the performance.</p>";
				  } else if (QLR_3/total > 0.1) {
					document.getElementById("container").innerHTML += "<p>The network quality or bandwidth may have limited the performance.</p>";
				  }
				  
				  document.getElementById("container").innerHTML += "<p><b>Average video bitrate:</b> " + parseInt(BBB/counter) + "-kbps</p>";

				  if (BBB/counter < 500) {
					document.getElementById("container").innerHTML += "<small><i>Did they select an active camera?</i></small><h3>Bitrate is really <span class='error'>bad</span></h3>";
				  } else if (BBB/counter < 1000) {
					document.getElementById("container").innerHTML += "<h3>Bitrate is poor</h3>";
				  } else if (BBB/counter < 2000) {
					document.getElementById("container").innerHTML += "<h3>Bitrate a bit low</h3>";
				  } else {
					document.getElementById("container").innerHTML += "<h3>Bitrate is <span class='success'>good</span></h3>";
				  }
				  
				  document.getElementById("container").innerHTML += "<p><b>Average video buffer length:</b> " + parseInt(BUFF/BUFFCCC) + "-ms</p>";
				  
				  if (BUFF/BUFFCCC > 500) {
					document.getElementById("container").innerHTML += "<h3>Video delay is really <span class='error'>bad</span></h3>";
				  } else if (BUFF/BUFFCCC > 200) {
					document.getElementById("container").innerHTML += "<h3>Video delay is poor</h3>";
				  } else if (BUFF/BUFFCCC > 100) {
					document.getElementById("container").innerHTML += "<h3>Video delay is sub-optimal</h3>";
				  } else {
					document.getElementById("container").innerHTML += "<h3>Video delay is <span class='success'>good</span></h3>";
				  }
				  
				  document.getElementById("container").innerHTML += "<p><b>Average video packet loss:</b> " + (parseInt(PAK*1000/PAKCCC)/1000.0) + "%</p>";
				  
				  if (PAK/PAKCCC > 3) {
					document.getElementById("container").innerHTML += "<h3>Packet loss is extremely <span class='error'>bad</span>; Must Fix This</h3>";
				  } else if (PAK/PAKCCC > 0.8) {
					document.getElementById("container").innerHTML += "<h3>Packet loss is quite <span class='error'>bad</span>; expect problems with audio and video</h3>";
				  } else if (PAK/PAKCCC > 0.15) {
					document.getElementById("container").innerHTML += "<h3>Packet loss is a bit high; might be a testing-server issue though</h3>";
				  } else {
					document.getElementById("container").innerHTML += "<h3>Packet loss is <span class='success'>good</span></h3>";
				  }
				  
				  if (packetLossSpikes > 0) {
					document.getElementById("details").innerHTML += "<div class='warning'><b>Packet loss spikes detected:</b> " + packetLossSpikes + 
					  " occurrences of packet loss > 2%<br><small><i>(Spikes in packet loss can cause video freezes or audio dropouts even if average packet loss is low)</i></small></div>";
				  }
				  
				  // Render the codec details button (original code)
				  if (arr.some(data => data.codecs || data.detectedCodecs)) {
					document.getElementById("details").innerHTML += `<br />
					  <button id="view-codec-details" onclick="viewCodecDetails('${streamID}')">View Codec Support Details</button>
					`;
					
					window.viewCodecDetails = function(resultsId) {
					  window.open(`./codecs.html?results=${resultsId}`, '_blank');
					};
				  }
				}
			
			var xmlhttp = new XMLHttpRequest();
			var url = "https://record.vdo.workers.dev/?name="+streamID;

			xmlhttp.onreadystatechange = function() {
				if (this.readyState == 4 && this.status == 200) {
					var myArr = JSON.parse(this.responseText);
					document.getElementById("container").innerHTML = "";
					process(myArr);
				}
			};
			xmlhttp.open("GET", url, true);
			xmlhttp.send();

			
			
			function updateData(type, data) {
			
				if (type == "target"){
					bitrate.target = data;
				}
				
				if (type == "bitrate") {
					bitrate.data = data;
					plotData("bitrate", bitrate);
					plotData("bitrate", bitrate);
					plotData("bitrate", bitrate);
				}

				if (type == "buffer") {
					buffer.data = data;
					plotData("buffer", buffer);
					plotData("buffer", buffer);
					plotData("buffer", buffer);
				}

				if (type == "packetloss") {
					packetloss.data = data;
					plotData("packetloss", packetloss);
					plotData("packetloss", packetloss);
					plotData("packetloss", packetloss);
				}
			}

			function plotData(type, stat) {
				var canvas;
				var context;
				var yScale;

				canvas = document.getElementById(stat.element);
				context = canvas.getContext("2d");

				if (isNaN(stat.data)) {
					stat.data = 0;
				}

				var text = (canvas.previousElementSibling.innerHTML = stat.data);

				var height = context.canvas.height;
				var width = context.canvas.width;

				var borderWidth = 5;
				var offset = borderWidth * 2;

				// Create gradient
				var grd = context.createLinearGradient(0, 0, 0, height);

				if (type == "bitrate") {
					
					if (stat.target <= 3000){
						grd.addColorStop(0, "#33C433");
						grd.addColorStop(0.7, "#33C433");
						grd.addColorStop(0.8, "#F3F304");
						grd.addColorStop(0.92, "#F30404");
					} else if (stat.target <= 4000){
						grd.addColorStop(0, "#33C433");
						grd.addColorStop(0.5, "#33C433");
						grd.addColorStop(0.8, "#F3F304");
						grd.addColorStop(0.92, "#F30404");
					} else {
						grd.addColorStop(0, "#33C433");
						grd.addColorStop(0.3, "#33C433");
						grd.addColorStop(0.8, "#F3F304");
						grd.addColorStop(0.92, "#F30404");
					}
					
				} else {
					// Higher values are red
					grd.addColorStop(0, "#F30404");
					grd.addColorStop(0.3, "#F3F304");
					grd.addColorStop(0.7, "#33C433");
				}

				context.strokeStyle = "white";
				context.fillStyle = grd;
				//context.fillStyle = "#009933";
				//context.imageSmoothingEnabled = true;

				yScale = height / stat.max;

				if (stat.data > stat.max) {
					stat.data = stat.max;
				}

				if (type == "packetloss" && stat.data == 0.0) {
					stat.data = 0.1;
				}


				var x = width - 1;
				var y = height - stat.data * yScale;
				var w = 1;

				context.fillStyle = grd;
				context.fillRect(x, y, w, height);

				// shift everything to the left:
				var imageData = context.getImageData(1, 0, width - 1, height);
				context.putImageData(imageData, 0, 0);
				// now clear the right-most pixels:
				context.clearRect(width - 1, 0, 1, height);
			}
		</script>
	</body>
</html>
